package com.alex.utils.http;

import java.io.IOException;
import java.net.URL;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import com.alex.utils.http.audit.HttpAuditProvider;
import com.alex.utils.http.cache.CachedHttpResult;
import com.alex.utils.http.cache.HttpCacheProvider;
import com.alex.utils.http.proxy.SocksProxy;
import com.alex.utils.http.proxy.SocksProxySocketFactory;

public class RequestBuilder {

	private static final DummyCookieStore DUMMY_COOKIE_STORE = new DummyCookieStore();

	private HttpRequestBase httpRequest;
	
	private HttpSession session;
	
	private HttpAuditProvider auditProvider;
	
	private HttpCacheProvider cacheProvider;
	
	private HttpClient httpClient;
	
	public RequestBuilder(HttpRequestBase httpRequest, HttpClient httpClient) {
		this.httpRequest = httpRequest;
		this.httpClient = httpClient;
		SocksProxySocketFactory.get().setProxy(null);
		gzip(true);
	}
	
	public void gzip(boolean flag) {
		if (flag) {
			httpRequest.removeHeaders("Accept-Encoding");
			httpRequest.addHeader("Accept-Encoding", "gzip,deflate");
		} else {
			httpRequest.removeHeaders("Accept-Encoding");
			httpRequest.addHeader("Accept-Encoding", "deflate");
		}
	}

	public RequestBuilder session(HttpSession session) {
		this.session = session;
		return this;
	}
	
	public RequestBuilder header(String name, String value) {
		httpRequest.addHeader(name, value);
		return this;
	}
	
	public RequestBuilder mozillaUserAgent() {
		httpRequest.addHeader("User-Agent", "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 Ubuntu/9.10 (karmic) Firefox/3.6.8");
		return this;
	}
	
	public RequestBuilder entity(HttpEntity entity) throws IOException {
		if (httpRequest instanceof HttpPost) {
			((HttpPost) httpRequest).setEntity(entity);
			return this;
		} else {
			throw new IOException("Can spesify entity only for post requests");
		}
	}

	public String getResultAsString() throws IOException {
		HttpResult httpResult = getResult();
		return httpResult.getResponseBodyAsString();
	}
	
	public byte[] getResultAsByteArray() throws IOException {
		return getResult().getResponseBodyAsByteArray();
	}
	
	public HttpResult getResult() throws IOException {
		HttpContext context = null;
		if (session != null) {
			context = session.getHttpContext();
		} else {
			context = new BasicHttpContext();
			context.setAttribute(ClientContext.COOKIE_STORE, DUMMY_COOKIE_STORE);
		}
		
		URL url = httpRequest.getURI().toURL();
		
		if (httpRequest instanceof HttpGet && cacheProvider != null) {
			byte[] entry = cacheProvider.getEntry(url);
			if (entry != null) {
				return new CachedHttpResult(entry);
			}
		}
		
		HttpResponse httpResponse = httpClient.execute(httpRequest, context);
		
		String finalUrl = HttpUtils.getFinalURL(httpResponse, context);
		
		if (auditProvider != null) {
			auditProvider.audit(httpRequest, httpResponse);
		}
		
		if (httpRequest instanceof HttpGet && cacheProvider != null && cacheProvider.isCacheble(httpRequest, httpResponse)) {
			CachedHttpResult httpResult = new CachedHttpResult(httpResponse, finalUrl);
			String uri = httpRequest.getURI().toString();
			
			cacheProvider.putEntry(url, httpResult.getEntry());
			if (!uri.equals(finalUrl)) {
				cacheProvider.putEntry(new URL(finalUrl), httpResult.getEntry());
			}
			
			return httpResult;
		}
		
		return new DefaultHttpResult(httpResponse, finalUrl);
	}

	public RequestBuilder referer(String referer) {
		httpRequest.addHeader("Referer", referer);
		return this;
	}

	public void execute() throws IOException {
		HttpResult result = getResult();
		result.close();
	}

	public RequestBuilder accept(String contentType) {
		httpRequest.addHeader("Accept", contentType);
		return this;
	}

	public RequestBuilder acceptLanguage(String lang) {
		httpRequest.addHeader("Accept-Language", lang);
		return this;
	}

	public RequestBuilder acceptCharset(String charset) {
		httpRequest.addHeader("Accept-Charset", charset);
		return this;
	}
	
	public RequestBuilder socks(SocksProxy proxy) {
		SocksProxySocketFactory.get().setProxy(proxy);
		return this;
	}
	
	public RequestBuilder cache(HttpCacheProvider cacheProvider) {
		this.cacheProvider = cacheProvider;
		return this;
	}
	
	public RequestBuilder audit(HttpAuditProvider auditProvider) {
		this.auditProvider = auditProvider;
		return this;
	}
}